unit battleeventframeunit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, ExtCtrls, ComCtrls, StdCtrls, Buttons,
  fpjson, database, battleventconditionsselection, eclistframeunit;

type

  { TBattleEventFrame }

  TBattleEventFrame = class(TFrame)
    ClearPageButton: TButton;
    ConditionButton: TButton;
    CopyPageButton: TButton;
    DeletePageButton: TButton;
    BattleECListFrame: TECListFrame;
    NewPageButton: TButton;
    PastePageButton: TButton;
    ScopeComboBox: TComboBox;
    ConditionLabel: TLabel;
    ScopeLabel: TLabel;
    PagePanel: TPanel;
    PagesTabControl: TTabControl;
    PageOptionsPanel: TPanel;
    PageDataPanel: TPanel;
    PageConditionPanel: TPanel;
    PageScopePanel: TPanel;
    ButtonAvailabilityTimer: TTimer;
    procedure ClearPageButtonClick(Sender: TObject);
    procedure ConditionButtonClick(Sender: TObject);
    procedure CopyPageButtonClick(Sender: TObject);
    procedure DeletePageButtonClick(Sender: TObject);
    procedure ButtonAvailabilityTimerTimer(Sender: TObject);
    procedure NewPageButtonClick(Sender: TObject);
    procedure PagesTabControlChange(Sender: TObject);
    procedure PastePageButtonClick(Sender: TObject);
    procedure ScopeComboBoxChange(Sender: TObject);
  private
    IsLoading, IsUpdatingTabs: Boolean;
    SelectedPageIndex: Integer;
    Db: TDatabase;
    Pages: TJSONArray;
    Troop: TJSONObject;

    procedure RefreshTabs;
    procedure LoadPage(NewPageIndex: Integer);
    function GetSelectedPage: TJSONObject;
    procedure LoadCondition(Page: TJSONObject);
    function FormatCondition(Cond: TJSONObject): String;
    procedure LoadScope(Page: TJSONObject);
    procedure LoadEventCommands(Page: TJSONObject);
    procedure FillScopeComboBox;
    procedure RefreshButtonAvailability;
  public
    procedure SetDatabase(ADb: TDatabase);
    procedure SetEditedPages(APages: TJSONArray; ATroop: TJSONObject);
  end;

resourcestring
  rsNoCondition = 'None (the event never runs)';
  rsTurnEndingCondition = 'Turn ending';
  rsTurnCondition = 'Turn #%s';
  rsEnemyHp = 'Enemy #%d''s HP <= %s';
  rsActorHp = 'Actor #%d''s HP <= %s';
  rsSwitch = '{%s} is ON';

  rsRepeatPerBattle = 'Once per battle';
  rsRepeatPerTurn = 'Once per turn';
  rsRepeatWhileTrue = 'While conditions is true';

implementation

uses
  echelper, Math, clipboardhelper;

{$R *.lfm}

{ TBattleEventFrame }

procedure TBattleEventFrame.PagesTabControlChange(Sender: TObject);
begin
  LoadPage(PagesTabControl.TabIndex);
end;

procedure TBattleEventFrame.PastePageButtonClick(Sender: TObject);
var
  PastedObject: TJSONObject;
  TypeString: String;
begin
  TypeString := DataTypeStrings[DATA_TYPE_ID_BATTLE_EVENT_PAGE];
  if ClipboardHasData(TypeString) then begin
    PastedObject := GetJsonObjectFromClipboard(TypeString);
    if PastedObject <> nil then begin
      Pages.Insert(PagesTabControl.TabIndex + 1, PastedObject);
      RefreshTabs;
      PagesTabControl.TabIndex := PagesTabControl.TabIndex + 1;
      LoadPage(PagesTabControl.TabIndex);
    end;
  end;

end;

procedure TBattleEventFrame.ScopeComboBoxChange(Sender: TObject);
var
  Page: TJSONObject = nil;
begin
  if (not IsLoading) and (not IsUpdatingTabs) then
    Page := GetSelectedPage;

  if Page <> nil then
    Page.Integers['span'] := ScopeComboBox.ItemIndex;
end;

procedure TBattleEventFrame.NewPageButtonClick(Sender: TObject);
begin
  Pages.Insert(PagesTabControl.TabIndex + 1, CreateEmptyBattleEventPage);
  RefreshTabs;
  PagesTabControl.TabIndex := PagesTabControl.TabIndex + 1;
  LoadPage(PagesTabControl.TabIndex);
end;

procedure TBattleEventFrame.DeletePageButtonClick(Sender: TObject);
begin
  Pages.Delete(PagesTabControl.TabIndex);
  RefreshTabs;
  PagesTabControl.TabIndex := Max(0, PagesTabControl.TabIndex);
  LoadPage(PagesTabControl.TabIndex);

  RefreshButtonAvailability
end;

procedure TBattleEventFrame.ClearPageButtonClick(Sender: TObject);
begin
  Pages.Objects[SelectedPageIndex] := CreateEmptyBattleEventPage;
  LoadPage(SelectedPageIndex);
end;

procedure TBattleEventFrame.ConditionButtonClick(Sender: TObject);
var
  Page: TJSONObject = nil;
  Condition: TJSONObject = nil;
begin
  Page := GetSelectedPage;
  if (Page <> nil) then
    Page.Find('conditions', Condition);


  if BattleEventConditionsSelectionForm.ShowConditionSelector(Condition, Troop, Db) then
    Page.Objects['conditions'] := BattleEventConditionsSelectionForm.SelectedCondition.Clone as TJSONObject;
  LoadCondition(Page);
end;

procedure TBattleEventFrame.CopyPageButtonClick(Sender: TObject);
var
  Page: TJSONObject;
begin
  Page := GetSelectedPage;
  if (Page <> nil) then
    CopyDataToClipboard(DataTypeIds[DATA_TYPE_ID_BATTLE_EVENT_PAGE], Page);

  RefreshButtonAvailability
end;

procedure TBattleEventFrame.ButtonAvailabilityTimerTimer(Sender: TObject);
begin
  if (Db <> nil) and (Pages <> nil) then
    RefreshButtonAvailability
end;

procedure TBattleEventFrame.RefreshTabs;
var
  I: Integer;
  SavedTabIndex: Integer;
begin
  IsUpdatingTabs := True;
  SavedTabIndex := PagesTabControl.TabIndex;
  PagesTabControl.TabIndex := 0;


  with PagesTabControl.Tabs do begin
    BeginUpdate;
    Clear;
    for I := 0 to Pages.Count -1 do begin
      Add(IntToStr(I));
    end;

    EndUpdate;
  end;
  if SavedTabIndex < PagesTabControl.Tabs.Count then
    PagesTabControl.TabIndex := SavedTabIndex;

  IsUpdatingTabs := False;
end;

procedure TBattleEventFrame.LoadPage(NewPageIndex: Integer);
var
  Page: TJSONObject;
begin
  IsLoading := True;
  SelectedPageIndex := NewPageIndex;

  Page := GetSelectedPage;

  LoadCondition(Page);
  LoadScope(Page);
  LoadEventCommands(Page);

  IsLoading := False;
end;

function TBattleEventFrame.GetSelectedPage: TJSONObject;
begin
  GetSelectedPage := nil;

  if (Pages <> nil) and (SelectedPageIndex >= 0) and
                    (SelectedPageIndex < Pages.Count) then
    GetSelectedPage := Pages.Objects[SelectedPageIndex];
end;

procedure TBattleEventFrame.LoadCondition(Page: TJSONObject);
var
  Conditions: TJSONObject;
begin
  Page := GetSelectedPage;
  if (Page <> nil) and Page.Find('conditions', Conditions) then begin
    ConditionButton.Caption := FormatCondition(Conditions);
  end;
end;

function TBattleEventFrame.FormatCondition(Cond: TJSONObject): String;

  function IsCondTypeValid(CondType: String): Boolean;
  var
    IsValid: Boolean = False;
    IsValidJson: TJSONBoolean;
  begin
    if (Cond <> nil) and Cond.Find(CondType, IsValidJson) then
      IsValid := IsValidJson.AsBoolean;
    IsCondTypeValid := IsValid;
  end;

  function FormatTurnCondition: String;
  var
    TurnA: Integer = 0;
    TurnB: Integer = 0;
    TurnAJson, TurnBJson: TJSONNumber;

    function FormatTurnNumber: String;
    begin
      if (TurnA <> 0) and (TurnB <> 0) then
        FormatTurnNumber := '%d + %d*X'.Format([TurnA, TurnB])
      else if TurnB <> 0 then
        FormatTurnNumber := '%d*X'.Format([TurnB])
      else
        FormatTurnNumber := IntToStr(TurnA);
    end;

  begin
    if (Cond <> nil) and Cond.Find('turnA', TurnAJson) then
      TurnA := TurnAJson.AsInteger;
    if (Cond <> nil) and Cond.Find('turnB', TurnBJson) then
      TurnB := TurnBJson.AsInteger;

    FormatTurnCondition := rsTurnCondition.Format([FormatTurnNumber]);
  end;

  function FormatEnemyHpCondition: String;
  var
    EnemyHp: Integer = 0;
    EnemyIndex: Integer = 0;
    EnemyHpJson, EnemyIndexJson: TJSONNumber;
  begin
    if (Cond <> nil) and Cond.Find('enemyHp', EnemyHpJson) then
      EnemyHp := EnemyHpJson.AsInteger;
    if (Cond <> nil) and Cond.Find('enemyIndex', EnemyIndexJson) then
      EnemyIndex := EnemyIndexJson.AsInteger;

    FormatEnemyHpCondition := rsEnemyHp.Format([EnemyIndex + 1, IntToStr(EnemyHp) + '%']);
  end;

  function FormatActorHpCondition: String;
  var
    ActorHp: Integer = 0;
    ActorIndex: Integer = 0;
    ActorHpJson, ActorIndexJson: TJSONNumber;
  begin
    if (Cond <> nil) and Cond.Find('actorHp', ActorHpJson) then
      ActorHp := ActorHpJson.AsInteger;
    if (Cond <> nil) and Cond.Find('actorIndex', ActorIndexJson) then
      ActorIndex := ActorIndexJson.AsInteger;

    FormatActorHpCondition := rsActorHp.Format([
                             ActorIndex + 1,
                             IntToStr(ActorHp) + '%'
                           ]);
  end;

  function FormatSwitchCondition: String;
  var
    SwitchId: Integer = 1;
    SwitchIdJson: TJSONNumber;
  begin
    if (Cond <> nil) and Cond.Find('switchId', SwitchIdJson) then
      SwitchId := SwitchIdJson.AsInteger;
    FormatSwitchCondition := rsSwitch.Format([Db.GetSwitchName(SwitchId)]);
  end;

var
  CondStrings: TStringList;
  FinalString: String;
begin
  CondStrings := TStringList.Create;
  if IsCondTypeValid('turnEnding') then
    CondStrings.Add(rsTurnEndingCondition);
  if IsCondTypeValid('turnValid') then
    CondStrings.Add(FormatTurnCondition);
  if IsCondTypeValid('enemyValid') then
    CondStrings.Add(FormatEnemyHpCondition);
  if IsCondTypeValid('actorValid') then
    CondStrings.Add(FormatActorHpCondition);
  if IsCondTypeValid('switchValid') then
    CondStrings.Add(FormatSwitchCondition);

  CondStrings.Delimiter := #31; { TODO: use two-char delimeters }
  CondStrings.QuoteChar := ' ';
  if CondStrings.Count > 0 then
    FinalString := StringReplace(CondStrings.DelimitedText, #31, ', ', [rfReplaceAll, rfIgnoreCase])
  else
    FinalString := rsNoCondition;
  CondStrings.Free;

  FormatCondition := FinalString;
end;

procedure TBattleEventFrame.LoadScope(Page: TJSONObject);
var
  Scope: Integer = 0;
  ScopeJson: TJSONNumber;
begin
  if (Page <> nil) and Page.Find('span', ScopeJson) then
    Scope := ScopeJson.AsInteger;
  ScopeComboBox.ItemIndex := Scope;
end;

procedure TBattleEventFrame.LoadEventCommands(Page: TJSONObject);
var
  List: TJSONArray;
begin
  if (Page <> nil) then
    Page.Find('list', List);

  BattleECListFrame.SetTroop(Troop);
  BattleECListFrame.SetList(List);
end;

procedure TBattleEventFrame.FillScopeComboBox;
begin
  with ScopeComboBox.Items do begin
    BeginUpdate;
    Clear;
    Add(rsRepeatPerBattle);
    Add(rsRepeatPerTurn);
    Add(rsRepeatWhileTrue);
    EndUpdate;
  end;
end;

procedure TBattleEventFrame.RefreshButtonAvailability;
begin
  if (Db = nil) or (Pages = nil) then
    Exit;

  DeletePageButton.Enabled := Pages.Count > 1;
  PastePageButton.Enabled := ClipboardHasData(DataTypeStrings[DATA_TYPE_ID_BATTLE_EVENT_PAGE]);
end;

procedure TBattleEventFrame.SetDatabase(ADb: TDatabase);
begin
  Db := ADb;
  FillScopeComboBox;
  BattleECListFrame.SetDatabase(Db);
end;

procedure TBattleEventFrame.SetEditedPages(APages: TJSONArray;
  ATroop: TJSONObject);
begin
  Assert(Db <> nil, 'Can''t set edited pages if Db is nil');

  Pages := APages;
  Troop := ATroop;
  RefreshTabs;

  LoadPage(0);
  ButtonAvailabilityTimer.Enabled := True;
end;

end.

